/*
 * Permission.java
 *
 * Created on August 1, 2007, 10:03 AM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.auth.service.db;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Iterator;
import java.util.UUID;
import org.infoset.xml.Element;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.XMLException;
import org.milowski.db.DB;
import org.milowski.db.DBCache;
import org.milowski.db.DBConnection;
import org.milowski.db.DBIterator;
import org.milowski.db.DBObject;
import org.milowski.db.DBQueryHandler;
import org.milowski.db.DBResultConstructor;
import org.milowski.db.DBUpdateHandler;
import org.milowski.db.Slot;

/**
 *
 * @author alex
 */
public class RealmUser extends DBObject<AuthDB> implements XMLObject
{

   Realm realm;
   String alias;
   String name;
   String email;
   User user;
   
   /** Creates a new instance of Permission */
   public RealmUser(AuthDB db,int id,Realm realm,User user,String alias,String name,String email)
      throws SQLException
   {
      super(db,id);
      this.realm = realm;
      this.alias = alias;
      this.user = user;
      this.name = name;
      this.email = email;
   }
   
   public void delete() 
      throws SQLException
   {
      DBConnection connection = db.getConnection();
      try {
         db.LOG.info("Delete id "+id);
         connection.deleteById(AuthDB.DELETE_REALM_USER_ALIAS, id);
         connection.deleteById(AuthDB.DELETE_REALM_USER_GROUPS, id);
         connection.deleteById(AuthDB.DELETE_REALM_USER, id);
         db.realmUserCaches.get(realm).remove(id);
      } finally {
         db.release(connection);
      }
   }
   
   public Realm getRealm() {
      return realm;
   }

   public String getAlias()
   {
      return alias==null ? user.getAlias() : alias;
   }

   public boolean changeAlias(final String alias)
      throws SQLException
   {
      if (alias!=null && this.alias!=null && alias.equals(this.alias)) {
         // alias is the same so no change is needed
         return true;
      }
      if (alias!=null && this.alias==null && alias.equals(user.getAlias())) {
         // alias is the same as inherited so no change is needed
         return true;
      }
      if (alias==null && user.getAlias()==null) {
         // The user doesn't have an alias and this change requires an inherited alias
         // so this can't be allowed
         return false;
      }
      if (!db.isRealmUserAliasAvailable(realm,user,alias)) {
         return false;
      }
      if (this.alias==null) {
         // Only set the realm user's alias if the alias is not null
         if (alias!=null) {
            DBConnection connection = db.getConnection();
            try {
               connection.update(AuthDB.CREATE_REALM_USER_ALIAS, new DBUpdateHandler() {
                  public void prepare(PreparedStatement s)
                     throws SQLException
                  {
                     s.setInt(1,id);
                     s.setString(2,alias);
                     s.setString(3,id+"-"+alias);
                  }
               });
            } finally {
               db.release(connection);
            }
         }
      } else {
         if (alias!=null) {
            // just update
            DBConnection connection = db.getConnection();
            try {
               connection.update(AuthDB.UPDATE_REALM_USER_ALIAS, new DBUpdateHandler() {
                  public void prepare(PreparedStatement s)
                     throws SQLException
                  {
                     s.setString(1,alias);
                     s.setString(2,id+"-"+alias);
                     s.setInt(3,id);
                  }
               });
            } finally {
               db.release(connection);
            }
         } else {
            // alias is null, delete so we inherit
            DBConnection connection = db.getConnection();
            try {
               connection.deleteById(AuthDB.DELETE_REALM_USER_ALIAS, id);
            } finally {
               db.release(connection);
            }
         }
      }
      String prevAlias = getAlias();
      if (prevAlias!=null) {
         db.realmUserCaches.get(realm).removeByName(prevAlias);
      }
      this.alias = alias;
      return true;
   }
   
   public boolean hasRole(final Role role)
      throws SQLException
   {
      boolean result = user.hasRole(role);
      if (!result) {
         final Slot<Boolean> found = new Slot<Boolean>(Boolean.FALSE);
         final DBCache<UUID,Group> cache = db.realmGroupCaches.get(realm);
         DBConnection connection = db.getConnection();
         try {
            connection.query(AuthDB.GROUP_BY_REALM_USER, new DBQueryHandler() {
               public void prepare(PreparedStatement s)
                  throws SQLException
               {
                  s.setInt(1,realm.getId());
                  s.setInt(2,id);
               }
               public void onResults(ResultSet set) 
                  throws SQLException
               {
                  while (!found.get() && set.next()) {
                     Group group = cache.get(set.getInt(1));
                     found.set(group.hasRole(role));
                  }
               }
            });
         } finally {
            db.release(connection);
         }
         
         result = found.get();
      }
      return result;
   }

   public boolean hasPermission(Permission permission)
      throws SQLException 
   {
      boolean found = user.hasPermission(permission);
      if (!found) {
         Iterator<Group> groups = getGroups();
         while (groups.hasNext()) {
            if (groups.next().hasPermission(permission)) {
               found = true;
            }
         }
      }
      return found;
   }
   
   public boolean isMemberOf(final Group group)
      throws SQLException
   {
      return group.hasMember(this);
   }

   public void addGroup(Group group)
      throws SQLException
   {
      if (!group.getRealm().equals(realm)) {
         throw new IllegalArgumentException("The group realm "+group.getRealm().getUUID()+" is not the realm of the user realm "+realm.getUUID()+"");
      }
      if (!isMemberOf(group)) {
         group.addMember(this);
      }
   }
   
   public boolean removeGroup(Group group)
      throws SQLException
   {
      return group.removeMember(this);
   }

   public Iterator<Group> getGroups() 
      throws SQLException
   {
      final Slot<Iterator<Group>> result = new Slot<Iterator<Group>>();
      final DBCache<UUID,Group> cache = db.realmGroupCaches.get(realm);
      final DBConnection connection = db.getConnection();
      try {
         connection.query(AuthDB.GROUP_BY_REALM_USER, new DBQueryHandler() {
            public boolean shouldClose() {
               return false;
            }
            public void prepare(PreparedStatement s)
               throws SQLException
            {
               s.setInt(1,realm.getId());
               s.setInt(2,id);
            }
            public void onResults(ResultSet set) 
               throws SQLException
            {
               result.set(new DBIterator<Group>(set,new DBResultConstructor<Group>() {
                  public Group newInstance(ResultSet set) 
                     throws SQLException
                  {
                     return cache.get(set.getInt(1));
                  }
               },db,connection));
            }
         });
      } catch (SQLException ex) {
         db.release(connection);
         throw ex;
      }
      return result.get();
   }
   
   public String getName()
   {
      return name==null ? user.getName() : name;
   }

   public void setName(String name)
      throws SQLException
   {
      if (name!=null && name.equals(user.getName())) {
         name = null;
      }
      final String theName = name;
      DBConnection connection = db.getConnection();
      try {
         connection.update(AuthDB.CHANGE_REALM_USER_NAME, new DBUpdateHandler() {
            public void prepare(PreparedStatement s)
               throws SQLException
            {
               if (theName==null) {
                  s.setNull(1,Types.VARCHAR);
               } else {
                  s.setString(1,theName);
               }
               s.setInt(2,id);
            }
         });
      } finally {
         db.release(connection);
      }
      this.name = name;
   }
   
   public String getEmail()
   {
      return email==null ? user.getEmail() : email;
   }

   public void setEmail(String email)
      throws SQLException
   {
      if (email!=null && email.equals(user.getEmail())) {
         email = null;
      }
      final String theEmail = email;
      DBConnection connection = db.getConnection();
      try {
         connection.update(AuthDB.CHANGE_REALM_USER_EMAIL, new DBUpdateHandler() {
            public void prepare(PreparedStatement s)
               throws SQLException
            {
               if (theEmail==null) {
                  s.setNull(1,Types.VARCHAR);
               } else {
                  s.setString(1,theEmail);
               }
               s.setInt(2,id);
            }
         });
      } finally {
         db.release(connection);
      }
      this.email = email;
   }
   
   public User getUser()
   {
      return user;
   }
   
   public boolean equals(Object obj) {
      if (!(obj instanceof RealmUser)) {
         return false;
      }
      RealmUser other = (RealmUser)obj;
      if (other.getUser().getUUID().equals(user.getUUID())) {
         return alias==null ? other.getAlias()==null : alias.equals(other.getAlias());
      } else {
         return false;
      }
   }
   
   public void generate(ItemConstructor constructor,ItemDestination dest)
      throws XMLException
   {
      Element top = constructor.createElement(XML.USER_NAME);
      top.setAttributeValue("id",user.getUUID().toString());
      String avalue = getAlias();
      // The alias shouldn't be null unless there is database corruption.
      if (avalue!=null) {
         top.setAttributeValue("alias",avalue);
      }
      dest.send(top);
      if (getName()!=null) {
         dest.send(constructor.createElement(XML.NAME_NAME));
         dest.send(constructor.createCharacters(getName()));
         dest.send(constructor.createElementEnd(XML.NAME_NAME));
      }
      if (getEmail()!=null) {
         dest.send(constructor.createElement(XML.EMAIL_NAME));
         dest.send(constructor.createCharacters(getEmail()));
         dest.send(constructor.createElementEnd(XML.EMAIL_NAME));
      }
      dest.send(constructor.createElementEnd(XML.USER_NAME));
   }
   
   public void generate(ItemConstructor constructor,ItemDestination dest,boolean contents)
      throws XMLException
   {
      generate(constructor,dest);
   }
}
